Spring Boot——整合websocket构建在线聊天室(群聊 / 私聊)

作者: 李多多 日期: 2019-07-20
Spring Boot
Spring Boot——整合websocket构建在线聊天室(群聊 / 私聊)

1.引入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2.注入对象ServerEndpointExporter

/*
*
*编写一个WebSocketConfig配置类,注入对象ServerEndpointExporter,
* 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/

@Configuration
public class WebSocketConfig {

@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}

3.WebSocket 具体实现类

package com.example.springbootwebsocket.mysocket;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

/**
*
* 虽然@Component默认是单例模式的,但是spring boot 还是会为每个websocket连接初始化一个bean,所以这里使用一个静态的set保存spring boot
*
* 创建的bean--MyWebSocket.
**/
@ServerEndpoint(value="/websocket/{nickname}")// websocket连接点映射.
@Component
public class MyWebSocket {

//用来存储每个客户端对应的MyWebSocket对象.
private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();
//用来记录sessionId和该session之间的绑定关系.
private static Map<String,Session> map = new HashMap<String,Session>();

private Session session;//当前会话的session.
private String nickname;//昵称.

/**
* 成功建立连接调用的方法.
*/
@OnOpen
public void onOpen(Session session,@PathParam("nickname") String nickname){
this.session = session;
this.nickname = nickname;
map.put(session.getId(), session);
webSocketSet.add(this);//加入set中.
this.session.getAsyncRemote().sendText(nickname+"上线了,(我的id号是"+session.getId()+")");
}

/**
* 连接关闭调用的方法.
*/
@OnClose
public void onClose(Session session){
webSocketSet.remove(this);//从set中移除.
map.remove(session.getId());
}

/**
* 收到客户端消息后调用的方法.
*/
@OnMessage
public void onMessage(String message,Session session,@PathParam("nickname") String nickname){

//message 不是普通的string ,而是我们定义的SocketMsg json字符串.
try {
SocketMsg socketMsg = new ObjectMapper().readValue(message, SocketMsg.class);


//单聊.
if(socketMsg.getType() == 1){

//单聊:需要找到发送者和接受者即可.
socketMsg.setFromUser(session.getId());//发送者.
//socketMsg.setToUser(toUser);//这个是由客户端进行设置.
Session fromSession = map.get(socketMsg.getFromUser());
Session toSession = map.get(socketMsg.getToUser());
if(toSession != null){
//发送消息.
fromSession.getAsyncRemote().sendText(nickname+":"+socketMsg.getMsg());
toSession.getAsyncRemote().sendText(nickname+":"+socketMsg.getMsg());
}else{
fromSession.getAsyncRemote().sendText("系统消息:对方不在线或者您输入的id号有误");
}
}else {
//群发给每个客户端.
broadcast(socketMsg,nickname);
}

} catch (JsonParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JsonMappingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

/**
* 发生错误时调用.
*/
public void onError(Session session,Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}

/**
* 群发的方法.
* @param
*/
private void broadcast(SocketMsg socketMsg ,String nickname){
for(MyWebSocket item:webSocketSet){
//发送消息.
item.session.getAsyncRemote().sendText(nickname+":"+socketMsg.getMsg());
}
}

}

4.创建消息对象SocketMsg

package com.example.springbootwebsocket.mysocket;

public class SocketMsg {
private int type;//聊天类型,0:群聊;1:单聊;
private String fromUser;//发送者.
private String toUser;//接受者. session.getId();
private String msg;//消息.
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getFromUser() {
return fromUser;
}
public void setFromUser(String fromUser) {
this.fromUser = fromUser;
}
public String getToUser() {
return toUser;
}
public void setToUser(String toUser) {
this.toUser = toUser;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}

5.static目录下创建客户端 webSocketTest.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>聊天室</title>
<style>

#message{
margin-top:20px;
border:1px solid gray;
padding:20px;
}
</style>

</head>
<body>

昵称:<input type="text" id="nickname" />
<button onclick="conectWebSocket()">连接服务器</button>
<button onclick="closeWebSocket()">断开连接</button>

<br />
消息:<input id="text" type="text" />
id号<input id="toUser" type="text" />
<button onclick="send()">发送消息</button>

<!-- 存放接收到的消息. -->
<div id="message">

</div>


<script type="text/javascript">

//连接对象.
var websocket = null;
var nickname = null;
function conectWebSocket(){
nickname = document.getElementById("nickname").value;
if(nickname == ''){
alert("请输入昵称");
return;
}
//判断当前的浏览器是否支持websocket.
if("WebSocket" in window){
websocket = new WebSocket("ws://localhost:8080/websocket/"+nickname);
}else{
alert("Not support websocket");
return false;
}

//连接成功的方法.
websocket.onopen = function(event){
setMessgeHtml("Loc MSG:连接成功");
}

//连接关闭.
websocket.onclose = function(event){
setMessgeHtml("Loc MSG:连接关闭");
}

//连接异常.
websocket.onerror = function(event){
setMessgeHtml("Loc MSG:连接异常");
}


websocket.onmessage = function(event){
setMessgeHtml(event.data);
}

}


function setMessgeHtml(msg){
var message = document.getElementById("message");
message.innerHTML += msg+"<br/>";
}

/**
发送消息.
*/
function send(){
var message = document.getElementById("text").value;
var toUser = document.getElementById("toUser").value;
var socketMsg = {msg:message,toUser:toUser};
if(toUser == ''){
socketMsg.type =0;//群聊.
}else{
socketMsg.type =1;//单聊.
}

//websocket.send(nickname+":"+message);
websocket.send(JSON.stringify(socketMsg));//将json对象转换为json字符串.
}


</script>



</body>
</html>

测试,打开多个登录窗口,输入http://127.0.0.1:8080/webSocketTest.html ,输入昵称点击连接即可登录成功,群聊直接发送消息即可,私聊输入完消息后,选择对应那个人的id号码就行,快去试试吧:

image.png

总结:了解了websocket底层是怎么实现的,websocket协议稍微麻烦,其实我们完全可以用它的子协议STOMP来实现本文所有的功能,因已封装的很好,用起来非常简单,实例:SpringBoot+STOMP 实现聊天室(单聊+多聊)及群发消息详解

参考:https://www.iteye.com/blog/412887952-qq-com-2405514